package com.stottlerhenke.simbionic.engine.core;


import java.util.ArrayList;

import com.stottlerhenke.dynamicscripting.DynamicScriptingWrapper;
import com.stottlerhenke.simbionic.api.SB_Exception;
import com.stottlerhenke.simbionic.api.SB_VarType;
import com.stottlerhenke.simbionic.common.SB_FileException;
import com.stottlerhenke.simbionic.common.SB_Logger;
import com.stottlerhenke.simbionic.common.SIM_Constants;
import com.stottlerhenke.simbionic.common.debug.DMFieldMap;
import com.stottlerhenke.simbionic.engine.SB_SimInterface;
import com.stottlerhenke.simbionic.engine.SB_SingletonBook;
import com.stottlerhenke.simbionic.engine.debug.EEventType;
import com.stottlerhenke.simbionic.engine.file.SB_StreamReader;
import com.stottlerhenke.simbionic.engine.parser.SB_ExpMethod;
import com.stottlerhenke.simbionic.engine.parser.SB_VarClass;
import com.stottlerhenke.simbionic.engine.parser.SB_Variable;


public class SB_ActionNode extends SB_Node
{
	protected SB_Action _action;
	
	protected boolean _bAction; //true if action should be used, false if class method
	protected String _classOrVariableName; //Used for class method actions
	protected String _methodName; //Used for class method actions
	protected ArrayList _args; //Arguments used in executing this action
	protected ArrayList _originalTransitionOrder = new ArrayList(); //Used to cache the original order
	

	public SB_ActionNode(SB_BehaviorElement owner, int nodeID)
	{
		super(owner, nodeID);
		_action = null;
		_bAction = true;
	}

	public ENodeType GetNodeType()
	{
		return ENodeType.kActionNode;
	}

	public String GetLogName()
	{
	  if( _bAction ) 
	    return _action.GetName();
	  else
	    return _classOrVariableName + "." + _methodName;
	}

	public void initialize(ArrayList transitions, SB_SingletonBook book)
			throws SB_Exception
	{
		super.initialize(transitions, book);
		
		if( _bAction ) 
		  _bindings.initParamBindings(_action.GetParams());
		else
		  _bindings.initParamBindings();
		
		_originalTransitionOrder.addAll(_transitionEdges);
	}

	/**
	 * Replaces the action for this node if it matches the new action's name.
	 * @param newMethod the action with which to replace existing action
	 */
	public void replaceAction(SB_Action newMethod)
	{
	    if (_action.GetName().equals(newMethod.GetName()))
	    {
	        _action = newMethod;
	    }
	}			
	
	public void Execute(SB_Entity entity, SB_ExecutionFrame contextFrame,
			SB_SingletonBook book) throws SB_Exception
	{
		// only execute the node when first entering it (ie not on every
		// clock tick afterward while remaining in the node)
		if (!contextFrame.HasNodeBeenInvoked())
		{
			contextFrame.SetNodeBeenInvoked(true);
			
			// apply the variable bindings for the node before execution.
			_bindings.ApplyForVariables(entity, contextFrame);
			// prepare the argument list for the action
			_args = new ArrayList();
			_bindings.ApplyForInvocation(entity, contextFrame, _args );
			//(_action.GetParams()).GetNumParams());
			
			// execute the associated action
			/* Reserved Actions are deprecated
			if (SB_Action.IsReserved(_action.GetId()))
			{
				if (SIM_Constants.DEBUG_INFO_ON)
					book.getLogger().log(
							"[" + entity.toString() + ",STK " + contextFrame.GetStackDepth()
									+ "] ACTION:\tinvoking " + _action.toString(),
							SB_Logger.ACTION);
				SB_Action.DoReservedAction(_action, entity, args);
				if (SIM_Constants.DEBUG_INFO_ON)
					book.getLogger().log(
							"[" + entity.toString() + ",STK " + contextFrame.GetStackDepth()
									+ "] ACTION:\tcompleted", SB_Logger.ACTION);
			} else */
			{
				// invoke the action in the simulator
				long id = SB_SimInterface.TranslateId(entity.GetId());
				// prepare the parameters for the action
				ArrayList params = new ArrayList();
				SB_SimInterface.ConvertVariables(_args, params);
				ArrayList snapshot = new ArrayList();
				if (SIM_Constants.AI_DEBUGGER)
				{
					DMFieldMap fields = new DMFieldMap();
					fields.ADD_ID_FIELD("entity", entity.GetId()._id);
					fields.ADD_INT_FIELD("frame", contextFrame.GetStackDepth());
					fields.ADD_STR_FIELD("funcName", GetLogName());
					contextFrame.GetDebugger().RecordEvent(
							EEventType.kEVT_ACTION_INVOKED, fields);
				}
				if (SIM_Constants.DEBUG_INFO_ON)
					book.getLogger().log(
							"[" + entity.toString() + ",STK " + contextFrame.GetStackDepth()
									+ "] ACTION:\tinvoking " + GetLogName(),
							SB_Logger.ACTION);
				
				 //Caught for engine runtime exception handling
		    try 
				{
		    	if( _bAction )
		    		_action.doAction(id, params);
		    	else
		    		doClassMethod(entity, contextFrame, _args, book);
				}
		    catch(SB_Exception ex)
				{
		    	//Log the exception
                if(SIM_Constants.DEBUG_INFO_ON)
                {
                    book.getLogger().log("[" + entity.toString() + ",STK " + contextFrame.GetStackDepth()
                                        + "] ACTION:\tcaused an exception", SB_Logger.ERROR);		    
                    book.getLogger().logException(ex);
                }
		    	
		    	SB_EntityData state = entity.GetState();
		  		state.GetExecStack().HandleException(ex, contextFrame);
		  		return;
				}
		    
				if (SIM_Constants.DEBUG_INFO_ON)
					book.getLogger().log(
							"[" + entity.toString() + ",STK " + contextFrame.GetStackDepth()
									+ "] ACTION:\tcompleted", SB_Logger.ACTION);
				// map the return values back to internal variables
				_args = SB_SimInterface.ConvertParams(params, contextFrame.GetBook());
			}
			// map the action's "out" parameters back to bound variables
			_bindings.ApplyForReturn(entity, contextFrame, _args);
		}
	}

	/**
	 * Loads a single action node from the specification.
	 * 
	 * @param reader
	 *          the stream reader from which to read
	 * @throws SB_FileException
	 */
	public void load(SB_StreamReader reader, SB_SingletonBook book)
			throws SB_FileException
	{
		super.load(reader, book);
		// read in the associated action
		String action = reader.readPString();
		
		int dotIndex = action.indexOf(".");
		if(dotIndex >= 0 )
		{
			_bAction = false;
			_classOrVariableName = action.substring(0, dotIndex);
			_methodName = action.substring(dotIndex + 1, action.length());
		}
		else
		{
			try
			{
				// store the action itself instead of the string name
				_action = book.getBehaviorRegistry().getAction(action);
			} 
			catch (SB_Exception e)
			{
				throw new SB_FileException(e.getMessage());
			}
		}
	}
	
	/**
	 * Call a method based on the data from an SB_VarClass object
	 * 
	 * @param entity
	 * @param contextFrame
	 * @param args an Array of SB_Variable objects
	 * @param book
	 * @throws SB_Exception
	 */
	protected void doClassMethod(SB_Entity entity, SB_ExecutionFrame contextFrame, ArrayList args, SB_SingletonBook book)
		throws SB_Exception
	{
	  if (SIM_Constants.DEBUG_INFO_ON)
			book.getLogger().log(
					"[" + entity.toString() + ",STK " + contextFrame.GetStackDepth()
							+ "] ACTION:\tClass method", SB_Logger.ACTION);
	  
	  if (SIM_Constants.DEBUG_INFO_ON)
			book.getLogger().log(
					"[" + entity.toString() + ",STK " + contextFrame.GetStackDepth()
							+ "] ACTION:\tlooking for " + _classOrVariableName, SB_Logger.ACTION);
	  
		//First, try and lookup the variable
		SB_Variable var = null;
		
		if( contextFrame.IsVariable(_classOrVariableName) )
		  var = contextFrame.GetVariable(_classOrVariableName);
		else //Try to load as static
		{			
		  if (SIM_Constants.DEBUG_INFO_ON)
				book.getLogger().log(
						"[" + entity.toString() + ",STK " + contextFrame.GetStackDepth()
								+ "] ACTION:\tfailed to find existing variable (" + _classOrVariableName + "), accessing as static ", SB_Logger.ACTION);
		  
			var = new SB_VarClass((String) contextFrame.GetBook().getUserClassMap().getJavaClassName(_classOrVariableName));
		}
		
		if( var == null)
		{
		  if (SIM_Constants.DEBUG_INFO_ON)
				book.getLogger().log(
						"[" + entity.toString() + ",STK " + contextFrame.GetStackDepth()
								+ "] ACTION:\tCan't find variable " + _classOrVariableName, SB_Logger.ACTION);

			throw new SB_Exception("Can't evaluate class method: " + _classOrVariableName + "." + _methodName);
		}
		
	  if(var.GetType().getState() >= SB_VarType.kCLASS_START.getState())
	    SB_ExpMethod.EvaluateClassMemberMethod(((SB_VarClass)var).getClassClass(), ((SB_VarClass)var).getClassObject(), _methodName, SB_SimInterface.ConvertObjects(args), contextFrame.GetBook());
	  else
	    throw new SB_Exception(_classOrVariableName + " is not a class variable but a " + SB_Variable.TypeToString(var.GetType()) );

	}

	@Override
	public SB_TransitionEdge FollowTransition(SB_Entity p, SB_ExecutionFrame contextFrame) throws SB_Exception
	{
		// copies from SB_Node.FollowTransition
		if (!contextFrame.HasNodeBeenInvoked())
		{
			return null;
		}
		  
		//If we are at a choice point, re-order transition edges
		String choicePointName = null;
		if(_action != null && CHOOSE.equals(_action.GetName()))
		{
			choicePointName = _args.get(0).toString();
			reorderTransitions(choicePointName, p);
		}
		
		SB_TransitionEdge edge = super.FollowTransition(p, contextFrame);
		
		//Reset the transitions back to the original order if changed
		if(_action != null && CHOOSE.equals(_action.GetName()))
		{
			_transitionEdges.clear();
			_transitionEdges.addAll(_originalTransitionOrder);
			
			int index = _transitionEdges.indexOf(edge);
			DynamicScriptingWrapper.getInstance().actionSelectedBySimbionic(choicePointName, index, p.GetName());
		}
		
		return edge;
	}
	
	/**
	 * Use the dynamic scripting wrapper to reorder the transitions out of the SB_ActionNode
	 * based on the DS selection procedure.
	 * 
	 * @throws SB_Exception
	 */
	private void reorderTransitions(String state, SB_Entity p) throws SB_Exception
	{
		if(_transitionEdges.size() > 1)
		{
			int[] order = DynamicScriptingWrapper.getInstance().orderActions(state, p.GetName(), _transitionEdges.size());
			
			//Using a subscript, may be shorter
			//if(order.length != _transitionEdges.size())
			//	throw new SB_Exception("Dynamic scripting ordering list (" +order.length + ") is not the same size as the choice action transition list (" +_transitionEdges.size() + ")");
			
			//System.out.print("[");
			ArrayList temp = new ArrayList();
			for(int x = 0; x < order.length; x++)
			{
				temp.add(_transitionEdges.get(order[x]));
				//System.out.print(order[x] + ",");
			}
			//System.out.println("]");
			
			_transitionEdges.clear();
			_transitionEdges.addAll(temp);
		}
	}
	
	final static String CHOOSE = "chooseDS";
	
}